package db

import (
	"context"
	"fmt"
	"log/slog"
	"time"

	clientv3 "go.etcd.io/etcd/client/v3"
	"go.uber.org/zap"
	"google.golang.org/grpc"
)

// ? 参考:
// ? https://github.com/etcd-io/etcd/tree/main/tests/integration/clientv3/examples
// ? https://blog.csdn.net/weixin_42758299/article/details/120777575

var Etcd *EtcdClient

type EtcdClient struct {
	*clientv3.Client
}

// 初始化Etcd客户端(包括鉴权)
func InitEtcd(cfg clientv3.Config) error {
	etcd, err := EtcdConn(cfg)
	if err != nil {
		return err
	}

	authStatus, err := etcd.AuthStatus()
	if err != nil {
		return fmt.Errorf("获取Etcd鉴权模式失败: %w", err)
	}
	if !authStatus {
		_ = etcd.AddRootUser(cfg)
		_ = etcd.AuthEnable()
	}
	return nil
}

// 关闭Etcd连接
func CloseEtcd() {
	if Etcd != nil {
		Etcd.Close()
	}
}

// 创建Etcd连接
func EtcdConn(cfg clientv3.Config) (*EtcdClient, error) {
	var password string
	if len(cfg.Password) >= 8 {
		password = cfg.Password[0:8] + "--------"
	}
	slog.Info("Etcd连接信息", slog.String("endpoints", fmt.Sprintf("%v", cfg.Endpoints)), slog.String("username", cfg.Username), slog.String("password", password))

	cli, err := clientv3.New(cfg)
	if err != nil {
		return nil, fmt.Errorf("Etcd连接失败: %w", err) //nolint:staticcheck
	}

	// 检查Etcd服务连接状态
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	var lastError error
	for _, endpoint := range cfg.Endpoints {
		_, err = cli.Status(ctx, endpoint)
		if err != nil {
			slog.Warn("Etcd连接检查失败", slog.String("endpoint", endpoint), slog.String("error", err.Error()))
			lastError = err // 保留最后一个错误，便于返回
		} else {
			slog.Info("Etcd连接成功", slog.String("endpoint", endpoint))
			Etcd = &EtcdClient{cli}
			return &EtcdClient{cli}, nil
		}
	}

	// 如果所有 Endpoints 都失败，返回错误
	cli.Close()
	if lastError != nil {
		return nil, fmt.Errorf("所有Etcd Endpoints连接失败: %w", lastError)
	}

	return nil, fmt.Errorf("Etcd连接失败")
}

// 用户Etcd连通性测试
func (e *EtcdClient) UserConnTest(points []string, username, password, key string) bool {
	timeout := 2 * time.Second
	// 初始化后使用鉴权模式连接Etcd
	conf := clientv3.Config{
		Endpoints:   points,
		DialTimeout: timeout,
		Username:    username,
		Password:    password,
		DialOptions: []grpc.DialOption{
			grpc.WithBlock(),
		},
		LogConfig: &zap.Config{
			Level:    zap.NewAtomicLevelAt(zap.ErrorLevel),
			Encoding: "json",
		},
	}
	cli, err := clientv3.New(conf)
	if err != nil {
		slog.Error("Etcd连接失败", slog.String("error", err.Error()))
		return false
	}
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	getResp, err := cli.Get(ctx, key)
	if err != nil {
		slog.Error("Etcd获取键失败", slog.String("error", err.Error()))
		return false
	}
	// 如果没有查询到数据, 则返回false
	if getResp.Count <= 0 {
		slog.Error("Etcd获取键失败", slog.String("error", "no data"))
		return false
	}
	return true
}

// 判断是否开启了鉴权
func (e *EtcdClient) AuthStatus() (bool, error) {
	ctx := context.Background()
	authStatusResponse, err := e.Auth.AuthStatus(ctx)
	if err != nil {
		slog.Error("Etcd鉴权状态获取失败", slog.String("error", err.Error()))
		return false, err
	}
	if authStatusResponse.Enabled {
		slog.Info("Etcd鉴权已开启")
		return true, nil
	} else {
		slog.Info("Etcd鉴权未开启")
		return false, nil
	}
}

// 开启鉴权
func (e *EtcdClient) AuthEnable() error {
	_, err := e.Auth.AuthEnable(context.Background())
	if err != nil {
		slog.Error("Etcd鉴权开启失败", slog.String("error", err.Error()))
		return err
	}
	slog.Info("Etcd鉴权开启成功")
	return nil
}

// 关闭鉴权
func (e *EtcdClient) AuthDisable() error {
	_, err := e.Auth.AuthDisable(context.Background())
	if err != nil {
		slog.Error("Etcd鉴权关闭失败", slog.String("error", err.Error()))
		return err
	}
	slog.Info("Etcd鉴权关闭成功")
	return nil
}

// 添加root用户, 从配置文件读取用户名和密码, role名与root用户名相同
func (e *EtcdClient) AddRootUser(cfg clientv3.Config) error {
	ctx := context.Background()
	// etcdctl role add root
	// 添加root角色
	_, err := e.RoleAdd(ctx, cfg.Username)
	if err != nil {
		slog.Error("Etcd角色添加失败", slog.String("error", err.Error()))
	}
	slog.Info("Etcd角色添加成功", slog.String("role", cfg.Username))

	// etcdctl user add root
	// 添加root用户
	_, err = e.UserAdd(ctx, cfg.Username, cfg.Password)
	if err != nil {
		slog.Error("Etcd用户添加失败", slog.String("error", err.Error()))
	}
	slog.Info("Etcd用户添加成功", slog.String("user", cfg.Username))

	// etcdctl user grant-role root root
	// 授权角色
	_, err = e.UserGrantRole(ctx, cfg.Username, cfg.Username)
	if err != nil {
		slog.Error("Etcd用户授权失败", slog.String("error", err.Error()))
	}
	slog.Info("Etcd用户授权成功", slog.String("user", cfg.Username), slog.String("role", cfg.Username))

	return err
}

// 添加用户
func (e *EtcdClient) AddUser(username, password string) error {
	ctx := context.Background()
	// 添加user角色
	roleGet, err := e.RoleGet(ctx, username)
	if err != nil {
		slog.Error("获取Etcd角色失败", slog.String("error", err.Error()))
	}
	if roleGet == nil {
		_, err := e.RoleAdd(ctx, username)
		if err != nil {
			slog.Error("添加Etcd角色失败", slog.String("error", err.Error()))
		}
		slog.Info("添加Etcd角色成功", slog.String("role", username))
	}

	// 添加user用户
	userGet, err := e.UserGet(ctx, username)
	if err != nil {
		slog.Error("获取Etcd用户失败", slog.String("error", err.Error()))
	}
	if userGet == nil {
		_, err = e.UserAdd(ctx, username, password)
		if err != nil {
			slog.Error("添加Etcd用户失败", slog.String("error", err.Error()))
		}
		slog.Info("添加Etcd用户成功", slog.String("user", username))
	} else {
		_, err = e.UserChangePassword(ctx, username, password)
		if err != nil {
			slog.Error("Etcd用户密码修改失败", slog.String("error", err.Error()))
		}
		slog.Info("Etcd用户密码修改成功", slog.String("user", username))
	}

	// 授权角色
	_, err = e.UserGrantRole(ctx, username, username)
	if err != nil {
		slog.Error("Etcd用户授权失败", slog.String("error", err.Error()))
	}
	slog.Info("Etcd用户授权成功", slog.String("user", username), slog.String("role", username))

	return nil
}

// 删除用户
func (e *EtcdClient) DeleteUser(username string) error {
	// 删除授权角色
	_, err := e.UserRevokeRole(context.Background(), username, username)
	if err != nil {
		slog.Error("删除Etcd用户授权失败", slog.String("error", err.Error()))
	}
	slog.Info("删除Etcd用户授权成功", slog.String("user", username), slog.String("role", username))

	// 删除角色
	_, err = e.RoleDelete(context.Background(), username)
	if err != nil {
		// log.Println("- RoleDelete", err.Error())
		slog.Error("删除Etcd角色失败", slog.String("error", err.Error()))
	}
	slog.Info("删除Etcd角色成功", slog.String("role", username))

	// 删除用户
	_, err = e.UserDelete(context.Background(), username)
	if err != nil {
		slog.Error("删除Etcd用户失败", slog.String("error", err.Error()))
	}
	slog.Info("删除Etcd用户成功", slog.String("user", username))

	return nil
}

// 删除用户所有键
func (e *EtcdClient) DeleteUserKey(path string) {
	// 删除用户的所有键
	_, err := e.Delete(context.Background(), path, clientv3.WithPrefix())
	if err != nil {
		slog.Error("删除Etcd用户键失败", slog.String("error", err.Error()))
	}
	slog.Info("删除Etcd用户键成功", slog.String("path", path))
}

// 角色授予权限
// rangeEnd 不为空时, 为前缀匹配
// permType READ=0 WRITE=1 READWRITE=2
func (e *EtcdClient) UpdateRoleGrantPermission(roleName, key, rangeEnd string, permType clientv3.PermissionType) {
	ctx := context.Background()
	// rangeEnd 不为空时, 为前缀匹配
	if rangeEnd != "" {
		rangeEnd = clientv3.GetPrefixRangeEnd(rangeEnd)
	}
	// 删除原有键权限
	_, err := e.RoleRevokePermission(ctx, roleName, key, rangeEnd)
	if err != nil {
		slog.Error("删除Etcd角色权限失败", slog.String("error", err.Error()))
	}
	slog.Info("删除Etcd角色权限成功", slog.String("role", roleName))

	// 授权键权限
	_, err = e.RoleGrantPermission(
		ctx,
		roleName,                          // role name
		key,                               // key
		rangeEnd,                          // range end
		clientv3.PermissionType(permType), // READ=0 WRITE=1 READWRITE=2
	)
	if err != nil {
		slog.Error("授权Etcd角色权限失败", slog.String("error", err.Error()))
	}
	slog.Info("授权Etcd角色权限成功", slog.String("role", roleName))
}

// 设置键
func (e *EtcdClient) PutKey(key, value string) error {
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()
	_, err := e.Put(ctx, key, value)
	if err != nil {
		slog.Error("设置Etcd键失败", slog.String("error", err.Error()))
		return err
	}
	slog.Info("设置Etcd键成功", slog.String("key", key), slog.String("value", value))
	return nil
}

// 获取键
func (e *EtcdClient) GetKey(key string) (res string, err error) {
	var resp *clientv3.GetResponse
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	resp, err = e.Get(ctx, key)
	if err != nil {
		slog.Error("获取Etcd键失败", slog.String("error", err.Error()))
		return "", err
	}
	slog.Info("获取Etcd键成功", slog.String("key", key), slog.Int64("count", resp.Count))

	if resp.Count >= 1 {
		return string(resp.Kvs[0].Value), nil
	}
	return "", nil
}

// 获取键(使用前缀模式)
func (e *EtcdClient) GetPrefixKey(key string) (res []string, err error) {
	var resp *clientv3.GetResponse
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	resp, err = e.Get(ctx, key, clientv3.WithPrefix())
	if err != nil {
		slog.Error("使用前缀模式获取Etcd键失败", slog.String("error", err.Error()))
		return nil, err
	}
	slog.Info("使用前缀模式获取Etcd键成功", slog.Int64("count", resp.Count))

	switch resp.Count {
	case 0:
		return nil, nil
	case 1:
		return append(res, string(resp.Kvs[0].Value)), nil
	default:
		for _, v := range resp.Kvs {
			slog.Info("使用前缀模式获取Etcd键成功", slog.String("key", string(v.Key)), slog.String("value", string(v.Value)))
			res = append(res, string(v.Value))
		}
		return res, nil
	}
}

// 删除键
// prefix 使用前缀模式
func (e *EtcdClient) DelKey(key string, prefix bool) (err error) {
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()
	if !prefix {
		_, err = e.Delete(ctx, key)
	} else {
		_, err = e.Delete(ctx, key, clientv3.WithPrefix())
	}
	if err != nil {
		slog.Error("删除Etcd键失败", slog.String("error", err.Error()))
		return err
	}
	slog.Info("删除Etcd键成功", slog.String("key", key))
	return nil
}
